/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */
#pragma once

#include "Key.h"
#include "Keys.h"
#include "EnvelopeTemplates.h"
#include <xml/Stream.h>
#include <string>
#include <filesystem/String.hpp>

#ifdef __APPLE__
#include <math/Vector3.h>
#include <math/iphone/matrix_impl.h>
#include <TargetConditionals.h>
#endif

namespace liba
{
namespace animate
{

class EnvelopeBase : public xml::ObjectANSI
{
public:
	enum Behavior { CONSTANT, LINEAR, REPEAT, OSCILLATE, OFFSET };
	static converter::EnumPair<char,Behavior> BehaviorNames[];

	EnvelopeBase()
	{
		behavior[0] = CONSTANT;
		behavior[1] = CONSTANT;
	}

	virtual bool on_attribute( xml::Provider * prov, const std::string & name, const std::string & value );
protected:
	Behavior behavior[2];
};

template< typename K >
class Envelope : public EnvelopeBase
{
public:
	virtual bool on_node( xml::Provider * prov, const std::string & name )
	{
		if( name == "Keys" )
		{
			return prov->parse_object( &keys );
		}
		return false;
	}

	time_float get_length()const
	{
		if(!keys.size())
			return 0;

		return (keys.end()-1)->time;
	}

	K evaluate(float time)
	{
		/* if there's no key, the value is undefined */
		if ( keys.size() == 0 )
			return K();

		/* if there's only one key, the value is constant */
		if ( keys.size() == 1 )
			return keys.begin()->value;

		typename Keys< K >::iterator start = keys.begin();
		typename Keys< K >::iterator end = keys.end()-1;

		int noff = 0;
		if ( time < start->time )
		{
			switch ( behavior[ 0 ] )
			{
			case CONSTANT:
				return start->value;
			case REPEAT:
				time = range( time, start->time, end->time, &noff );
				noff = 0;
				break;
			case OSCILLATE:
				time = range( time, start->time, end->time, &noff );
				if( noff % 2 )
					time = end->time - start->time - time;
				noff = 0;
				break;
			case OFFSET:
				time = range( time, start->time, end->time, &noff );
				break;
			case LINEAR:
				return start->value + start->linear * (time - start->time);
			}
		}
		else if ( time >= end->time )
		{
			switch ( behavior[ 1 ] )
			{
			case CONSTANT:
				return end->value;
			case REPEAT:
				time = range( time, start->time, end->time, &noff );
				noff = 0;
				break;
			case OSCILLATE:
				time = range( time, start->time, end->time, &noff );
				if ( noff % 2 )
					time = end->time - start->time - time;
				noff = 0;
				break;
			case OFFSET:
				time = range( time, start->time, end->time, &noff );
				break;
			case LINEAR:
				return end->value + end->linear * (time - end->time);
			}
		}

		typename Keys< K >::iterator key0 = keys.begin();
		while( time > (key0 + 1)->time )
			++key0;

		typename Keys< K >::iterator key1 = key0 + 1;

		float t = (time - key0->time);// (key1->time - key0->time);

		K val = key0->value + (key0->linear + (key0->quadratic + key0->cubic * t)* t)*t;
		if( noff )
		{
			val += (end->value - start->value)*float(noff);
		}
		return val;
	}
private:
 	Keys< K > keys;
};

#ifdef __APPLE__
#if (TARGET_IPHONE_SIMULATOR == 0) && (TARGET_OS_IPHONE == 1)

template<>
class Envelope< math::Vector3<float> > : public EnvelopeBase
{
	typedef math::Vector3<float> K;
public:
	virtual bool on_node( xml::Provider * prov, const std::string & name )
	{
		if( name == "Keys" )
		{
			return prov->parse_object( &keys );
		}
		return false;
	}
	
	double get_length()const
	{
		if(!keys.size())
			return 0;
		
		return (keys.end()-1)->time;
	}
	
	K evaluate(float time)
	{
		/* if there's no key, the value is undefined */
		if ( keys.size() == 0 )
			return K();
		
		/* if there's only one key, the value is constant */
		if ( keys.size() == 1 )
			return keys.begin()->value;
#ifdef __clang__ __llvm_		
		typename Keys< K >::iterator start = keys.begin();
		typename Keys< K >::iterator end = keys.end()-1;
#else
		Keys< K >::iterator start = keys.begin();
		Keys< K >::iterator end = keys.end()-1;
#endif
	
		int noff = 0;
		if ( time < start->time )
		{
			switch ( behavior[ 0 ] )
			{
				case CONSTANT:
					return start->value;
				case REPEAT:
					time = range( time, start->time, end->time, &noff );
					noff = 0;
					break;
				case OSCILLATE:
					time = range( time, start->time, end->time, &noff );
					if( noff % 2 )
						time = end->time - start->time - time;
					noff = 0;
					break;
				case OFFSET:
					time = range( time, start->time, end->time, &noff );
					break;
				case LINEAR:
					return start->value + start->linear * (time - start->time);
			}
		}
		else if ( time >= end->time )
		{
			switch ( behavior[ 1 ] )
			{
				case CONSTANT:
					return end->value;
				case REPEAT:
					time = range( time, start->time, end->time, &noff );
					noff = 0;
					break;
				case OSCILLATE:
					time = range( time, start->time, end->time, &noff );
					if ( noff % 2 )
						time = end->time - start->time - time;
					noff = 0;
					break;
				case OFFSET:
					time = range( time, start->time, end->time, &noff );
					break;
				case LINEAR:
					return end->value + end->linear * (time - end->time);
			}
		}
#ifdef __clang__ __llvm_		
		typename Keys< K >::iterator key0 = keys.begin();
#else
		Keys< K >::iterator key0 = keys.begin();
#endif
		while( time > (key0 + 1)->time )
			key0++;
#ifdef __clang__ __llvm_
		typename Keys< K >::iterator key1 = key0 + 1;
#else
		Keys< K >::iterator key1 = key0 + 1;
#endif
		
		float t = (time - key0->time);// (key1->time - key0->time);

		K val;
		CubicSplineCalculate(key0->value.data(), key0->linear.data(), key0->quadratic.data(), key0->cubic.data(), t, val.data());

//		K val2 = key0->value + (key0->linear + (key0->quadratic + key0->cubic * t)* t)*t;
		if( noff )
		{
			val += (end->value - start->value)*float(noff);
		}
		return val;
	}
private:
	Keys< K > keys;
};

#endif
#endif

template<typename Str>
class EnvelopeStringBase : public EnvelopeBase
{
public:
	virtual bool on_node( xml::Provider * prov, const std::string & name )
	{
		if( name == "Keys" )
		{
			return prov->parse_object( &keys );
		}
		return false;
	}

	double get_length()const
	{
		return (keys.end()-1)->time;
	}

	Str evaluate(float time)
	{
		/* if there's no key, the value is undefined */
		if ( keys.size() == 0 )
			return Str();

		/* if there's only one key, the value is constant */
		if ( keys.size() == 1 )
			return keys.begin()->value;

		typename Keys< Str >::iterator start = keys.begin();
		typename Keys< Str >::iterator end = keys.end()-1;

		int noff = 0;
		if ( time < start->time )
		{
			switch ( behavior[ 0 ] )
			{
			case CONSTANT:
			case LINEAR:
				return start->value;
			case OFFSET:
			case REPEAT:
				time = range( time, start->time, end->time, &noff );
				noff = 0;
				break;
			case OSCILLATE:
				time = range( time, start->time, end->time, &noff );
				if( noff % 2 )
					time = end->time - start->time - time;
				noff = 0;
				break;
			}
		}
		else if ( time >= end->time )
		{
			switch ( behavior[ 1 ] )
			{
			case CONSTANT:
			case LINEAR:
				return end->value;
			case OFFSET:
			case REPEAT:
				time = range( time, start->time, end->time, &noff );
				noff = 0;
				break;
			case OSCILLATE:
				time = range( time, start->time, end->time, &noff );
				if ( noff % 2 )
					time = end->time - start->time - time;
				noff = 0;
				break;
			}
		}

		typename Keys< Str >::iterator key0 = keys.begin();
		while( time > (key0 + 1)->time )
			++key0;
		typename Keys< Str >::iterator key1 = key0 + 1;

		return key0->value;
	}
private:
 	Keys< Str > keys;
};

template<>
class Envelope<std::string> : public EnvelopeStringBase<std::string>
{};

template<>
class Envelope<std::wstring> : public EnvelopeStringBase<std::wstring>
{};

template<>
class Envelope<filesystem::String> : public EnvelopeStringBase<filesystem::String>
{};

template< typename K >
class EnvelopeTCB : public EnvelopeBase
{
public:
	EnvelopeTCB()
	:	loop_keys(false)
	{}
	virtual bool on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
	{
		if(name == "LoopKeys")
			return converter::convert(value, &loop_keys);

		return EnvelopeBase::on_attribute(prov, name, value);
	}

	virtual bool on_node( xml::Provider * prov, const std::string & name )
	{
		if( name == "Keys" )
		{
			return prov->parse_object( &keys );
		}
		return false;
	}

	double get_length()const
	{
		if(!keys.size())
			return 0;

		return (keys.end()-1)->time;
	}

	K evaluate_gradient(float time)
	{
		/* if there's no key, the value is 0 */
		if ( keys.size() == 0 )
			return K();

		/* if there's only one key, the value is constant */
		if ( keys.size() == 1 )
			return K();

		/* find the first and last keys */
		typename KeysTCB< K >::iterator start = keys.begin();
		typename KeysTCB< K >::iterator end = keys.end()-1;

		typename KeysTCB< K >::iterator key_prev = keys.end();
		if(loop_keys)
		{
			key_prev = keys.end()-2;
		}

		typename KeysTCB< K >::iterator key_next = keys.end();
		if(loop_keys)
		{
			key_next = keys.begin()+1;
		}

		int noff = 0;

		/* use pre-behavior if time is before first key time */
		if ( time < start->time )
		{
			switch ( behavior[ 0 ] )
			{
			case CONSTANT:
				return start->value;
			case REPEAT:
				time = range( time, start->time, end->time, &noff );
				noff = 0;
				break;
			case OSCILLATE:
				time = range( time, start->time, end->time, &noff );
				if ( noff % 2 )
					time = end->time - start->time - time;
				noff = 0;
				break;
			case OFFSET:
				time = range( time, start->time, end->time, &noff );
				break;
			case LINEAR:
				{
					K out = outgoing<K>( start, start+1, key_prev, keys.end() )
						/ ( (start+1)->time - start->time );
					return out;
				}
			}
		}

		/* use post-behavior if time is after last key time */
		else if ( time > end->time )
		{
			switch ( behavior[ 1 ] )
			{
			case CONSTANT:
				return end->value;
			case REPEAT:
				time = range( time, start->time, end->time, &noff );
				noff = 0;
				break;
			case OSCILLATE:
				time = range( time, start->time, end->time, &noff );
				if ( noff % 2 )
					time = end->time - start->time - time;
				noff = 0;
				break;
			case OFFSET:
				time = range( time, start->time, end->time, &noff );
				break;
			case LINEAR:
				{
					K in = incoming<K>( end-1, end, key_next, keys.end() )
						/ ( end->time - (end-1)->time );
					return in;
				}
			}
		}

		/* get the endpoints of the interval being evaluated */
		size_t key_id = 0;
		while ( time > keys[key_id + 1].time )
			key_id++;

		typename KeysTCB< K >::iterator key0 = keys.begin() + key_id;
		typename KeysTCB< K >::iterator key1 = key0 + 1;

		/* get interval length, time in [0, 1] */
		float t = ( time - key0->time ) / ( key1->time - key0->time );

		/* interpolate */
		if(key_id > 0)
			key_prev = key0 - 1;

		if(key_id + 2 < keys.size())
			key_next = key1+1;

		K tan_start = outgoing<K>( key0, key1, key_prev, keys.end() );
		K tan_end = incoming<K>( key0, key1, key_next, keys.end() );

		return key0->value * (6*t*t - 6*t) + 
			tan_start * (3*t*t - 4*t + 1) +
			key1->value * (-6*t*t + 6*t) +
			tan_end * (3*t*t - 2*t);
	}

	K evaluate(float time)
	{
		/* if there's no key, the value is 0 */
		if ( keys.size() == 0 )
			return K();

		/* if there's only one key, the value is constant */
		if ( keys.size() == 1 )
			return keys.begin()->value;

		/* find the first and last keys */
		typename KeysTCB< K >::iterator start = keys.begin();
		typename KeysTCB< K >::iterator end = keys.end()-1;

		typename KeysTCB< K >::iterator key_prev = keys.end();
		if(loop_keys)
		{
			key_prev = keys.end()-2;
		}

		typename KeysTCB< K >::iterator key_next = keys.end();
		if(loop_keys)
		{
			key_next = keys.begin()+1;
		}

		int noff = 0;

		/* use pre-behavior if time is before first key time */
		if ( time < start->time )
		{
			switch ( behavior[ 0 ] )
			{
			case CONSTANT:
				return start->value;
			case REPEAT:
				time = range( time, start->time, end->time, &noff );
				noff = 0;
				break;
			case OSCILLATE:
				time = range( time, start->time, end->time, &noff );
				if ( noff % 2 )
					time = end->time - start->time - time;
				noff = 0;
				break;
			case OFFSET:
				time = range( time, start->time, end->time, &noff );
				break;
			case LINEAR:
				{
					K out = outgoing<K>( start, start+1, key_prev, keys.end() )
						/ ( (start+1)->time - start->time );
					return out * ( time - start->time ) + start->value;
				}
			}
		}

		/* use post-behavior if time is after last key time */
		else if ( time > end->time )
		{
			switch ( behavior[ 1 ] )
			{
			case CONSTANT:
				return end->value;
			case REPEAT:
				time = range( time, start->time, end->time, &noff );
				noff = 0;
				break;
			case OSCILLATE:
				time = range( time, start->time, end->time, &noff );
				if ( noff % 2 )
					time = end->time - start->time - time;
				noff = 0;
				break;
			case OFFSET:
				time = range( time, start->time, end->time, &noff );
				break;
			case LINEAR:
				{
					K in = incoming<K>( end-1, end, key_next, keys.end() )
						/ ( end->time - (end-1)->time );
					return in * ( time - end->time ) + end->value;
				}
			}
		}

		/* get the endpoints of the interval being evaluated */
		size_t key_id = 0;
		while ( time > keys[key_id + 1].time )
			key_id++;

		typename KeysTCB< K >::iterator key0 = keys.begin() + key_id;
		typename KeysTCB< K >::iterator key1 = key0 + 1;

		K offset = K();
		if( noff )
		{
			offset = (end->value - start->value)*float(noff);
		}

		/* check for singularities first */
		if ( time == key0->time )
			return key0->value + offset;
		else if ( time == key1->time )
			return key1->value + offset;

		/* get interval length, time in [0, 1] */
		float t = ( time - key0->time ) / ( key1->time - key0->time );

		/* interpolate */
		K tan_start = K();
		K tan_end = K();

		if(key_id > 0)
			key_prev = key0 - 1;

		if(key_id + 2 < keys.size())
			key_next = key1+1;

		tan_start = outgoing<K>( key0, key1, key_prev, keys.end() );
		tan_end = incoming<K>( key0, key1, key_next, keys.end() );

		return key0->value * (2*t*t*t - 3*t*t + 1) + 
			tan_start * (t*t*t - 2*t*t + t) +
			key1->value * (-2*t*t*t + 3*t*t) +
			tan_end * (t*t*t - t*t) + offset;
	}

private:
	KeysTCB< K > keys;
	bool loop_keys;
};

template<>
class EnvelopeTCB<std::string> : public EnvelopeStringBase<std::string>
{};

template<>
class EnvelopeTCB<std::wstring> : public EnvelopeStringBase<std::wstring>
{};

template<>
class EnvelopeTCB<filesystem::String> : public EnvelopeStringBase<filesystem::String>
{};

} // namespace animate

namespace converter
{

template<>
struct ConverterTraits<char, animate::EnvelopeBase::Behavior>
:	public ConverterTraitsEnumBase<char, animate::EnvelopeBase::Behavior, animate::EnvelopeBase::BehaviorNames>
{};

} // namespace converter

} // namespace liba
using namespace liba;

